C-Style Preprocessor Directives

The C-Style preprocessor directives are compatible with similar directives that are existant in preprocessors for C compilers. OAsm does not change the behavior of these directives other than to change the initial character which introduces the directive from '#' to '%'.

%define

%define introduces a method to perform textual substitutions. In its simplest form it will just replace an identifier with some text:

%define HELLO\_WORLD "Hello World"

replaces all instances of the identifier HELLO_WORLD with the indicated string. For example after this definition the following statement:

db HELLO\_WORLD

will result in the string "Hello World" being compiled into the program.

%define is not limited to giving names to strings, it will do any sort of text substitution you want. That could include defining names for constants, or giving a name to an often-used instruction, for example.

In the below:

%define ARRAY\_MAX 4
mov eax,ARRAY\_MAX

the text "4" gets substituted for ARRAY_MAX prior to assembling the mov instruction, so what the assembler sees is:

mov eax,4

Note that definitions are also processed for substitution:

%define ONE 1
%define TWO (ONE + 1)
%define THREE (TWO + 1)
mov eax,THREE

is substituted multiple times during processing, with the final result being:

mov eax,((1 + 1) + 1)

OAsm will detect recursive substitutions and halt the substitution process, so things like:

%define ONE TWO
%define TWO ONE

will halt after detecting the recursion.

Also, the substitution text can be empty:

%define EMPTY
mov eax, EMPTY

results in the translated text:

mov eax,

which cannot be assembled and will result in a syntax error during assembly.

%define can also be used in its functional form for more advanced text replacement activities. In this form, the identifier is parameterized. During substitutions, arguments are also specified; and the original parameters are replaced with the arguments while substitution is occurring. For example:

%define mul(a,b)  a \* b
mov eax,mul(4,7)

is changed to:

mov eax,4 \* 7

prior to assembly. It is usually not a good idea to write this quite the way it was done however. The user may elect to put any text they want in the invocation, so one thing that can happen is they write:

mov eax, mul(4+3, 7+2)

This gets translated to:

mov eax, 4 + 3 \* 7 + 2

which was probably not the intent. Below is what was probably desired:

mov eax, (4+3) \* (7+2)

For this reason it is a good idea to fully parenthesize the parameters used in the original definition:

%define mul(a,b) ((a) \* (b))

so that the mov with mul gets translated to:

mov eax, ((4 + 3) * (7 + 2))

The extra set of parenthesis is used to prevent similar situations from happening when 'mul' is used as a subexpression of another expression.

Note that when using %define, substituted text is not evaluated in any way, other than to process substitutions on the identifier and any specified parameters. So the move statement in the last example would be visible to the assembler exactly as the substitutions dictate, and the assembler has to do further evaluation of the expression if it wants a constant value.

Within a definition, there are a couple of special-case substitutions that can occur with function-style definitions. In Stringizing, a parameter can be turned into a string. For example if you write:

%define STRINGIZE(str) \#str
db   STRINGIZE(Hello World)

quotes will be placed around the substituted parameter. So this translates to:

db   "Hello World"

prior to assembly.

In Tokenizing, new identifiers may be produced. For example:

%define Tokenizing(prefix, postfix) (prefix \#\# postfix + 4)
mov eax,Tokenizing(Hello,World)

would be translated to:

mov eax,HelloWorld + 4

prior to assembly.

Note that even though the hash character used to start a preprocessor statement has been changed to '%', hash is still used in stringizing and tokenizing.

Finally, OAsm supports the C99 extension to function-style definitions, which allows variable-length argument lists. For example:

%define mylist(first, ...)  dw first, \_\_VA\_ARGS\_\_

where __VA_ARGS__ means append all remaining arguments that are specified, could be used like this:

mylist(1)
mylist(1,2)
mylist(1,2,3,4,5)

and so on. These would expand to:

dw 1 dw 1,2 dw 1,2,3,4,5

Note that the name of the identifier that is replaced is case-sensitive with %define, for example HELLO_WORLD is not the same as Hello_World. There is a case-insensitive form of this directive %idefine which can be used to make these and other related identifiers the same.

Note: OAsm does not support overloading function-style macros.

For convenience OAsm allows %define definitions on the command line, which are useful for tailoring build behavior either directly or through the conditional processing facility.

%undef

**%undef** undoes a previous definition, so that it will not be considered for further substitutions (unless defined again).  For example:

%define REG\_EBX 3
%undef REG\_EBX
mov eax, REG\_EBX

results in no substitution occurring for the use of REG_EBX.

%error

%error displays an error, causing the assembler to not generate code. For example:

%error my new error

might display something like:

Error errdemo.asm(1): Error Directive: my new error

When the file is assembled.

%line

%line is used to change the file and line number listed in the error reporting. By default the error reporting indicates the file and line an error occur on. Sometimes in generated source code files, it is useful to refer to the line number in the original source code rather than in the file that is currently being assembled. %line accomplishes this by updating internal tables to indicate to the preprocessor that it should use alternate information when reporting an error. For example inserting the following at line 443 of test.asm:

    mov eax,^4

produces a syntax error when the code is assembled:

Error test.asm(443): Syntax Error.

If an additional %line directive is inserted:

    %line 10, "demo.c"
    mov eax,^4

the error changes to:

Error demo.c(10): Syntax Error

Note that once %line is used to change the line number and file name, OAsm remembers the new information and continues to increment the new line number each time it processes a line of source code.

%include

%include is used to start the interpretation of another source file. The current source file is suspended, and the new source file is loaded and assembled. Once the assembler is done with the new source file (and anything it also %includes) assembly resumes beginning where it left off in the current source file.

This facility is useful for example to hold preprocessor constants and structures that are shared between multiple source files. But the included file can include any valid assembler statement, including GLOBAL and EXTERN definitions.

For example if test.asm is being assembled and the statement:

%include "test1.asm"

is encountered, the assembly of test.asm will temporarily be suspended while OAsm goes off to assemble test1.asm. After it is done with test1.asm, OAsm remembers that it was previously assembling test.asm and picks up in that file where it left off (e.g. at the line after the %include statement).

This is not quite the same as specifying both test.asm and test1.asm on the command line. In the current example there is only one output file which contains the contents of both test.asm and test1.asm, where as if both were specified on the command line they would result in separate output files.

For convenience, an include path may be specified on the command line, and OAsm will search for included files both in the current directory, and in directories specified on that path.

%if

%if is a _%if-style conditional _that takes a numeric expression as an argument. If the numeric expression evaluates to a non-zero value, the result of the evaluation will be true, otherwise it will be false. Note that for purposes of this conditional, expressions are always evaluated; if an undefined identifier is used in a %if expression it is replaced with '0' and evaluation continues. Subsequent blocks of code will either be assembled if the result of the evaluation is non-zero, or ignored if the result of the evaluation is zero.

For example:

%define COLOR 3
%if  COLOR == 3
    mov eax,4
%endif

will result in the mov statement being assembled because the result of the argument evaluation is a nonzero value.

On the other hand:

%if ZERO
    mov eax,4
%endif

results in nothing being assembled because the value of 'ZERO' is zero.

See the section on Conditional Processing for more on %if-style conditionals.

%elif

%elif is a %elif-style conditional that takes a numeric expression as an argument. If the numeric expression evaluates to a non-zero value, the next block will be assembled, otherwise it will be ignored. As with %if, undefined symbols will be replaced with '0' for purposes of the evaulation.

For example:

%define COLOR 3
%if  COLOR == 2
    mov eax,4
%elif COLOR == 3
    inc eax
%endif

will result in the mov statement being ignored and the inc statement being assembled because the result of the %if argument evaluation is zero, and the result of the %elif argument evaluation is nonzero.

See the section on Conditional Processing for more on %elif-style conditionals.

%ifdef

%ifdef is a %if-style conditional that takes an identifer as an argument. If the identifier has been defined with a previous %define or %assign statement, the next block will be assembled, otherwise it will be ignored.

For example:

%define COLOR 3
%ifdef COLOR
    mov eax,4
%endif

will result in the mov statement being assembled because COLOR has been defined.

Note that a definition declared with %define or %assign must match the argument exactly, whereas a definition declared with %idefine or %iassign can differ in case and still match. %ifdef will not match identifiers declared with %macro or %imacro.

See the section on Conditional Processing for more on %if-style conditionals.

%ifndef

%ifndef is a %if-style conditional that takes an identifer as an argument. If the identifier has not been defined with a previous %define or %assign statement, the next block will be assembled, otherwise it will be ignored.

For example:

%define COLOR 3
%ifndef COLOR
    mov eax,4
%endif

will result in the mov statement being ignored because COLOR has been defined. Alternatively:

%undef COLOR
%ifndef COLOR
    mov eax,4
%endif

will result in the mov statement being assembled because COLOR is not currently defined.

Note that a definition declared with %define or %assign can have any difference from the argument and trigger assembly of the block, whereas a definition declared with %idefine or %iassign must differ in some way other than in case. %ifndef will assemble the following block for identifiers declared with %macro or %imacro.

See the section on Conditional Processing for more on %if-style conditionals.

%else

%else is used to select a block for assembly, when all previous %if-style conditionals and %elif-style conditionals in the same sequence have had their arguments evaluate to false. For example:

%define COLOR = 3
%if COLOR ==4
    mov eax,3
%else
    inc eax
%endif

will result in the mov being ignored, but the inc being assembled, because the evaluation of the %if argument is false.

See the section on Conditional Processing for more on %else.

%endif

%endif is used to end a conditional sequence. Once a conditional sequence is ended, code assembly proceeds as normal, unless the conditional sequence was itself nested within a block of a higher-level conditional sequence that is not being assembled.

See the section on Conditional Processing for more on %endif.